#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>

SDL_AudioDeviceID audio_dev = 0;

#define STB_VORBIS_NO_STDIO
#define STB_VORBIS_NO_PUSHDATA_API
#include "stb_vorbis.h"
#define MINIMP3_NO_SIMD
#define MINIMP3_NONSTANDARD_BUT_LOGICAL
#define MINIMP3_IMPLEMENTATION
#include "minimp3.h"

/*** lots of stuff that should be in minimp3.h really ***/
static void mp3dec_skip_id3v1(const uint8_t *buf, size_t *pbuf_size)
{
    size_t buf_size = *pbuf_size;
    const uint8_t *tag;
    uint32_t tag_size;
#ifndef MINIMP3_NOSKIP_ID3V1
    if (buf_size >= 128 && !memcmp(buf + buf_size - 128, "TAG", 3))
    {
        buf_size -= 128;
        if (buf_size >= 227 && !memcmp(buf + buf_size - 227, "TAG+", 4))
            buf_size -= 227;
    }
#endif
#ifndef MINIMP3_NOSKIP_APEV2
    if (buf_size > 32 && !memcmp(buf + buf_size - 32, "APETAGEX", 8))
    {
        buf_size -= 32;
        tag = buf + buf_size + 8 + 4;
        tag_size = (uint32_t)(tag[3] << 24) | (tag[2] << 16) | (tag[1] << 8) | tag[0];
        if (buf_size >= tag_size)
            buf_size -= tag_size;
    }
#endif
    *pbuf_size = buf_size;
}

static size_t mp3dec_skip_id3v2(const uint8_t *buf, size_t buf_size)
{
#define MINIMP3_ID3_DETECT_SIZE 10
#ifndef MINIMP3_NOSKIP_ID3V2
    if (buf_size >= MINIMP3_ID3_DETECT_SIZE && !memcmp(buf, "ID3", 3) && !((buf[5] & 15) || (buf[6] & 0x80) || (buf[7] & 0x80) || (buf[8] & 0x80) || (buf[9] & 0x80)))
    {
        size_t id3v2size = (((buf[6] & 0x7f) << 21) | ((buf[7] & 0x7f) << 14) | ((buf[8] & 0x7f) << 7) | (buf[9] & 0x7f)) + 10;
        if ((buf[5] & 16))
            id3v2size += 10; /* footer */
        return id3v2size;
    }
#endif
    return 0;
}

static void mp3dec_skip_id3(const uint8_t **pbuf, size_t *pbuf_size)
{
    uint8_t *buf = (uint8_t *)(*pbuf);
    size_t buf_size = *pbuf_size;
    size_t id3v2size = mp3dec_skip_id3v2(buf, buf_size);
    if (id3v2size)
    {
        if (id3v2size >= buf_size)
            id3v2size = buf_size;
        buf      += id3v2size;
        buf_size -= id3v2size;
    }
    mp3dec_skip_id3v1(buf, &buf_size);
    *pbuf = (const uint8_t *)buf;
    *pbuf_size = buf_size;
}

static int mp3dec_check_vbrtag(const uint8_t *frame, int frame_size, uint32_t *frames, int *delay, int *padding)
{
    static const char g_xing_tag[4] = { 'X', 'i', 'n', 'g' };
    static const char g_info_tag[4] = { 'I', 'n', 'f', 'o' };
    const uint8_t *tag;
    int flags;
#define FRAMES_FLAG     1
#define BYTES_FLAG      2
#define TOC_FLAG        4
#define VBR_SCALE_FLAG  8
    /* Side info offsets after header:
    /                Mono  Stereo
    /  MPEG1          17     32
    /  MPEG2 & 2.5     9     17*/
    bs_t bs[1];
    L3_gr_info_t gr_info[4];
    bs_init(bs, frame + HDR_SIZE, frame_size - HDR_SIZE);
    if (HDR_IS_CRC(frame))
        get_bits_mp3(bs, 16);
    if (L3_read_side_info(bs, gr_info, frame) < 0)
        return 0; /* side info corrupted */

    tag = frame + HDR_SIZE + bs->pos/8;
    if (memcmp(g_xing_tag, tag, 4) && memcmp(g_info_tag, tag, 4))
        return 0;
    flags = tag[7];
    if (!((flags & FRAMES_FLAG)))
        return -1;
    tag += 8;
    *frames = (uint32_t)(tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8) | tag[3];
    tag += 4;
    if (flags & BYTES_FLAG)
        tag += 4;
    if (flags & TOC_FLAG)
        tag += 100;
    if (flags & VBR_SCALE_FLAG)
        tag += 4;
    *delay = *padding = 0;
    if (*tag)
    {   /* extension, LAME, Lavc, etc. Should be the same structure. */
        tag += 21;
        if (tag - frame + 14 >= frame_size)
            return 0;
        *delay   = ((tag[0] << 4) | (tag[1] >> 4)) + (528 + 1);
        *padding = (((tag[1] & 0xF) << 8) | tag[2]) - (528 + 1);
    }
    return 1;
}

typedef struct
{
    int16_t *buffer;
    size_t samples; /* channels included, byte size = samples*sizeof(mp3d_sample_t) */
    int channels, hz, layer, avg_bitrate_kbps;
} mp3dec_file_info_t;

#define MP3D_E_MEMORY   -1
#define MP3D_E_PARAM    -2
#define MP3D_E_DECODE   -3
int mp3dec_load_buf(mp3dec_t *dec, uint8_t *buf, size_t buf_size, mp3dec_file_info_t *info)
{
    uint32_t iframes;
    int i, delay, padding, free_format_bytes = 0, frame_size = 0;
    const uint8_t *hdr;
    uint64_t detected_samples = 0;
    int to_skip = 0, ret = 0;
    mp3dec_frame_info_t frame_info;
    size_t allocated;
    size_t avg_bitrate_kbps = 0, frames = 0;
    mp3d_sample_t *alloc_buf;
    size_t skip;
    int samples;

    memset(info, 0, sizeof(*info));
    memset(&frame_info, 0, sizeof(frame_info));
    if (!dec || !buf || !info || (size_t)-1 == buf_size)
        return MP3D_E_PARAM;

    /* skip id3 */
    mp3dec_skip_id3((const uint8_t **)&buf, &buf_size);
    if (!buf_size)
        return 0;
    /* try to make allocation size assumption by first frame or vbr tag */
    mp3dec_init(dec);
    do
    {
        free_format_bytes = 0; frame_size = 0;
        i = mp3d_find_frame(buf, buf_size, &free_format_bytes, &frame_size);
        buf      += i;
        buf_size -= i;
        hdr = buf;
        if (i && !frame_size)
            continue;
        if (!frame_size)
            return 0;
        frame_info.channels = HDR_IS_MONO(hdr) ? 1 : 2;
        frame_info.hz = hdr_sample_rate_hz(hdr);
        frame_info.layer = 4 - HDR_GET_LAYER(hdr);
        frame_info.bitrate_kbps = hdr_bitrate_kbps(hdr);
        frame_info.frame_bytes = frame_size;
        samples = hdr_frame_samples(hdr)*frame_info.channels;
        if (3 != frame_info.layer)
            break;
        ret = mp3dec_check_vbrtag(hdr, frame_size, &iframes, &delay, &padding);
        if (ret > 0)
        {
            padding *= frame_info.channels;
            to_skip = delay*frame_info.channels;
            detected_samples = samples*(uint64_t)iframes;
            if (detected_samples >= (uint64_t)to_skip)
                detected_samples -= to_skip;
            if (padding > 0 && detected_samples >= (uint64_t)padding)
                detected_samples -= padding;
            if (!detected_samples)
                return 0;
        }
        if (ret)
        {
            buf      += frame_size;
            buf_size -= frame_size;
        }
        break;
    } while(1);
    allocated = MINIMP3_MAX_SAMPLES_PER_FRAME*sizeof(mp3d_sample_t);
    if (detected_samples)
        allocated += detected_samples*sizeof(mp3d_sample_t);
    else
        allocated += (buf_size/frame_info.frame_bytes)*samples*sizeof(mp3d_sample_t);
    info->buffer = (mp3d_sample_t*)malloc(allocated);
    if (!info->buffer)
        return MP3D_E_MEMORY;
    /* save info */
    info->channels = frame_info.channels;
    info->hz       = frame_info.hz;
    info->layer    = frame_info.layer;
    /* decode all frames */
    avg_bitrate_kbps = 0; frames = 0;
    do
    {
        if ((allocated - info->samples*sizeof(mp3d_sample_t)) < MINIMP3_MAX_SAMPLES_PER_FRAME*sizeof(mp3d_sample_t))
        {
            allocated *= 2;
            alloc_buf = (mp3d_sample_t*)realloc(info->buffer, allocated);
            if (!alloc_buf)
                return MP3D_E_MEMORY;
            info->buffer = alloc_buf;
        }
        samples = mp3dec_decode_frame(dec, buf, MINIMP3_MIN(buf_size, (size_t)INT_MAX), info->buffer + info->samples, &frame_info);
        buf      += frame_info.frame_bytes;
        buf_size -= frame_info.frame_bytes;
        if (samples)
        {
            if (info->hz != frame_info.hz || info->layer != frame_info.layer)
            {
                ret = MP3D_E_DECODE;
                break;
            }
            if (info->channels && info->channels != frame_info.channels)
            {
                ret = MP3D_E_DECODE;
                break;
            }
            samples *= frame_info.channels;
            if (to_skip)
            {
                skip = MINIMP3_MIN(samples, to_skip);
                to_skip -= skip;
                samples -= skip;
                memmove(info->buffer, info->buffer + skip, samples*sizeof(mp3d_sample_t));
            }
            info->samples += samples;
            avg_bitrate_kbps += frame_info.bitrate_kbps;
            frames++;
        }
    } while (frame_info.frame_bytes);
    if (detected_samples && info->samples > detected_samples)
        info->samples = detected_samples; /* cut padding */
    /* reallocate to normal buffer size */
    if (allocated != info->samples*sizeof(mp3d_sample_t) && info->samples > 1)
    {
        alloc_buf = (mp3d_sample_t*)realloc(info->buffer, info->samples*sizeof(mp3d_sample_t));
        if (!alloc_buf && info->samples)
            return MP3D_E_MEMORY;
        info->buffer = alloc_buf;
    }
    if (frames)
        info->avg_bitrate_kbps = avg_bitrate_kbps/frames;
    return ret;
}

/* important to include this AFTER we have the implementations */
#include "main.h"

typedef struct {
    char str_riff[4];
    int wav_size;
    char str_wave[8];
    int fmt_chunk_size;
    short audio_format;
    short channels;
    int sample_rate;
    int byte_rate;
    short frame_size;
    short bit_depth;
    char str_data[4];
    int data_bytes;
} wav_t;

typedef struct {
    char name[NAMELEN];
    int id, len, skip;
    uint8_t *data;  /* 16 bit stereo PCM data in WAV format of "len" size */
    uint8_t *buf;   /* original file data in any format of "size" size */
    uint64_t size;
} audio_t;
audio_t *sfx = NULL, *bgm = NULL;
int numsfx = 0, numbgm = 0, cursfx = -1, curbgm = -1;

/**
 * Detect if it's an audio asset that we know
 */
int audio_detect(uint8_t *buf)
{
    return !memcmp(buf, "OggS", 4) || !memcmp(buf, "ID3\4", 4) ||
        (!memcmp(buf, "RIFF", 4) && !memcmp(buf + 8, "WAVE", 4)) || !memcmp(buf, "MThd", 4);
}

/**
 * Decode an audio asset into a 16-bit stereo PCM data
 */
uint8_t *audio_decode(uint8_t *buf, uint64_t size, int *len)
{
    wav_t *wav = (wav_t*)buf;
    SDL_AudioCVT cvt;
    mp3dec_file_info_t info;
    mp3dec_t mp3d;
    uint8_t *ret = NULL;

    if(!buf || !size || !len) return NULL;
    *len = 0; memset(&info, 0, sizeof(info));
    if(!memcmp(buf, "OggS", 4)) {
        if((info.samples = stb_vorbis_decode_memory(buf, size, &info.channels, &info.hz, &info.buffer)) && info.buffer) {
            if(verbose > 1) debug("ogg decode samples %lu channels %u",info.samples,info.channels);
            if(!info.channels || !info.samples) { free(info.buffer); return NULL; }
            info.samples *= info.channels;
            SDL_BuildAudioCVT(&cvt, AUDIO_S16, info.channels, info.hz, AUDIO_S16, 2, 44100);
            cvt.len = info.samples * 2;
            /* limit PCM to one million samples */
doconv:     if(!cvt.len_mult) cvt.len_mult = 1;
            if(cvt.len > 1024*1024 * cvt.len_mult) cvt.len = 1024*1024 / cvt.len_mult;
            if(verbose > 1) debug(" audiocvt samples %lu channels %u len %u mult %u total %lu",
                info.samples, info.channels, cvt.len,cvt.len_mult, sizeof(wav_t) + cvt.len * cvt.len_mult);
            if(!(ret = (uint8_t*)malloc(sizeof(wav_t) + cvt.len * cvt.len_mult))) {
                if(info.buffer && info.buffer != (int16_t*)(buf + sizeof(wav_t))) free(info.buffer);
                engine_features |= EF_NOMEM;
                return NULL;
            }
            /* convert to 16 bit stereo 44100Hz */
            cvt.buf = ret + sizeof(wav_t);
            memcpy(cvt.buf, info.buffer, cvt.len);
            if(info.buffer && info.buffer != (int16_t*)(buf + sizeof(wav_t))) free(info.buffer);
            if(SDL_ConvertAudio(&cvt)) { free(ret); return NULL; }
            memset(ret, 0, sizeof(wav_t));
            /* add wav header */
            wav = (wav_t*)ret;
            memcpy(&wav->str_riff, "RIFF", 4);
            wav->wav_size = wav->data_bytes = info.samples * sizeof(int16_t);
            wav->wav_size += sizeof(wav_t);
            memcpy(&wav->str_wave, "WAVEfmt ", 8);
            wav->fmt_chunk_size = 16;
            wav->audio_format = 1;
            wav->channels = 2;
            wav->sample_rate = 44100;
            wav->byte_rate = 44100 * 2 * sizeof(int16_t);
            wav->frame_size = 2 * sizeof(int16_t);
            wav->bit_depth = sizeof(int16_t) << 3;
            memcpy(&wav->str_data, "data", 4);
            *len = wav->wav_size;
        }
    } else
    if(!memcmp(buf, "ID3\4", 4)) {
        if(mp3dec_load_buf(&mp3d, buf, size, &info) >= 0 && info.channels && info.samples && info.buffer) {
            if(verbose > 1) debug("mp3 decode samples %lu channels %u", info.samples, info.channels);
            SDL_BuildAudioCVT(&cvt, AUDIO_S16, info.channels, info.hz, AUDIO_S16, 2, 44100);
            cvt.len = info.samples * 2;
            goto doconv;
        }
    } else
    if(!memcmp(buf, "RIFF", 4) && !memcmp(buf + 8, "WAVE", 4) && wav->data_bytes && wav->channels && wav->bit_depth) {
        SDL_BuildAudioCVT(&cvt, wav->bit_depth == 8 ? AUDIO_U8 : (wav->bit_depth == 16 ? AUDIO_S16 :
            (wav->audio_format == 3 ? AUDIO_F32 : AUDIO_S32)), wav->channels, wav->sample_rate, AUDIO_S16, 2, 44100);
        if(verbose > 1) debug("wav decode samples %u channels %u", wav->data_bytes, wav->channels);
        if(sizeof(wav_t) + wav->data_bytes > size) wav->data_bytes = size - sizeof(wav_t);
        cvt.len = wav->data_bytes;
        info.channels = wav->channels;
        info.samples = (wav->data_bytes / (wav->channels * wav->bit_depth / 8));
        info.buffer = (int16_t*)(buf + sizeof(wav_t));
        goto doconv;
    }
    return ret;
}

/**
 * Add a sound effect to the list
 */
int audio_addsfx(int id, char *name, uint8_t *buf, uint32_t size)
{
    int i;

    if(sfx) {
        if(id != -1) {
            for(i = 0; i < numsfx; i++)
                if(sfx[i].id == id) return i;
        } else
        if(name && *name)
            for(i = 0; i < numsfx; i++)
                if(!strcmp(sfx[i].name, name)) return i;
    }
    if(buf && size > 32 && audio_detect(buf)) {
        i = numsfx++;
        if((sfx = (audio_t*)realloc(sfx, numsfx * sizeof(audio_t)))) {
            engine_ram += sizeof(audio_t) + size;
            memset(&sfx[i], 0, sizeof(audio_t));
            sfx[i].id = id;
            if(name && *name) strncpy(sfx[i].name, name, NAMELEN - 1);
            sfx[i].buf = buf;
            sfx[i].size = size;
            engine_features |= EF_SFX;
            return i;
        } else {
            engine_features |= EF_NOMEM;
            debug("memory allocation error, too many sound effects %u", numsfx);
            numsfx = 0;
        }
    }
    return -1;
}

/**
 * Add a background music to the list
 */
int audio_addbgm(int id, char *name, uint8_t *buf, uint32_t size)
{
    int i;

    if(bgm) {
        if(id != -1) {
            for(i = 0; i < numbgm; i++)
                if(bgm[i].id == id) return i;
        } else
        if(name && *name)
            for(i = 0; i < numbgm; i++)
                if(!strcmp(bgm[i].name, name)) return i;
    }
    if(buf && size > 32 && audio_detect(buf)) {
        i = numbgm++;
        if((bgm = (audio_t*)realloc(bgm, numbgm * sizeof(audio_t)))) {
            engine_ram += sizeof(audio_t) + size;
            memset(&bgm[i], 0, sizeof(audio_t));
            bgm[i].id = id;
            if(name && *name) strncpy(bgm[i].name, name, NAMELEN - 1);
            bgm[i].buf = buf;
            bgm[i].size = size;
            engine_features |= EF_BGM;
            return i;
        } else {
            engine_features |= EF_NOMEM;
            debug("memory allocation error, too many background music %u", numbgm);
            numbgm = 0;
        }
    }
    return -1;
}

/**
 * Load a sound effect
 */
void audio_load(int sf)
{
    if(!audio_dev || sf < 0 || sf >= numsfx || !sfx[sf].buf || sfx[sf].skip || sfx[sf].data) return;
    if(!(sfx[sf].data = audio_decode(sfx[sf].buf, sfx[sf].size, &sfx[sf].len))) {
        debug("unable to decode audio #%u (id %d name '%s')", sf, sfx[sf].id, sfx[sf].name);
        sfx[sf].skip = 1;
        return;
    }
    engine_ram += sfx[sf].len;
}

/**
 * Play a sound effect
 */
void audio_play(int sf)
{
    if(!audio_dev || sf < 0 || sf >= numsfx || !sfx[sf].buf || sfx[sf].skip) return;
    audio_load(sf);
    /* play audio if we have decoded PCM data */
    if(sfx[sf].data && sfx[sf].len) {
        audio_stop();
        SDL_QueueAudio(audio_dev, sfx[sf].data, sfx[sf].len);
        SDL_PauseAudioDevice(audio_dev, 0);
    }
}

/**
 * Silence audio
 */
void audio_stop(void)
{
    if(audio_dev) {
        SDL_PauseAudioDevice(audio_dev, 1);
        SDL_ClearQueuedAudio(audio_dev);
    }
}

/**
 * Free audio lists
 */
void audio_free(void)
{
    int i;

    if(sfx) {
        for(i = 0; i < numsfx; i++) {
            if(sfx[i].buf) free(sfx[i].buf);
            if(sfx[i].data) free(sfx[i].data);
        }
        free(sfx); sfx = NULL;
    }
    if(bgm) {
        for(i = 0; i < numbgm; i++) {
            if(bgm[i].buf) free(bgm[i].buf);
            if(bgm[i].data) free(bgm[i].data);
        }
        free(bgm); bgm = NULL;
    }
    numsfx = numbgm = 0;
}

/**
 * UI Controller
 */
void audio_ctrl(void)
{
    audio_t *audio = (tab == TAB_BGM ? bgm : sfx);
    int i, num = (tab == TAB_BGM ? numbgm : numsfx), cur = (tab == TAB_BGM ? curbgm : cursfx);
    char tmp[NAMELEN + 5], *s;

    switch(event.type) {
        case SDL_KEYDOWN:
            switch(event.key.keysym.sym) {
                case SDLK_UP: if(tab == TAB_BGM) curbgm--; else cursfx--; break;
                case SDLK_DOWN: if(tab == TAB_BGM) curbgm++; else cursfx++; break;
                case SDLK_HOME: if(tab == TAB_BGM) curbgm = 0; else cursfx = 0; break;
                case SDLK_END: if(tab == TAB_BGM) curbgm = numbgm - 1; else cursfx = numsfx - 1; break;
                case SDLK_PAGEUP: if(tab == TAB_BGM) curbgm -= ((screen_h - 42) / 18); else cursfx -= ((screen_h - 42) / 18); break;
                case SDLK_PAGEDOWN: if(tab == TAB_BGM) curbgm += ((screen_h - 42) / 18); else cursfx += ((screen_h - 42) / 18); break;
                case SDLK_RETURN: field = pressedfield = 0; break;
            }
        break;
        case SDL_KEYUP:
            switch(event.key.keysym.sym) {
                case SDLK_RETURN: field = pressedfield = 0; goto doexp;
            }
        break;
        case SDL_MOUSEBUTTONUP:
doexp:      if(field == 0 && pressedfield == 0 && cur >= 0 && cur < num && audio && audio[cur].buf) {
                if(audio[cur].name[0]) {
                    if((s = strrchr(audio[cur].name, '/'))) s++; else
                    if((s = strrchr(audio[cur].name, '\\'))) s++;
                    else s = audio[cur].name;
                } else
                    s = tmp + sprintf(tmp, "%s%05u.ogg", tab == TAB_BGM ? "bgm" : "sfx", cur);
                savedialog(NULL, s, audio[cur].buf, audio[cur].size);
            }
        break;
        case SDL_MOUSEBUTTONDOWN:
            if(my < 38 + screen_h - 21) {
                i = (scrcur + my - 38) / 18;
                if(tab == TAB_BGM) curbgm = i; else cursfx = i;
            }
        break;
    }
    if(curbgm >= numbgm) curbgm = numbgm - 1;
    if(curbgm < 0) curbgm = 0;
    if(cursfx >= numsfx) cursfx = numsfx - 1;
    if(cursfx < 0) cursfx = 0;
    cur = (tab == TAB_BGM ? curbgm : cursfx);
    if(scrcur > cur * 18) scrcur = cur * 18;
    if(scrcur < cur * 18 - (screen_h - 44)) scrcur = cur * 18 - (screen_h - 44);
}

/**
 * UI View
 */
void audio_view(void)
{
    audio_t *audio = (tab == TAB_BGM ? bgm : sfx);
    int num = (tab == TAB_BGM ? numbgm : numsfx), cur = (tab == TAB_BGM ? curbgm : cursfx);
    int i, tot = num * 18 - (screen_h - 21 - 4);

    /* failsafes */
    if((tab == TAB_SFX && !numsfx) || (tab == TAB_BGM && !numbgm)) {
        if(!numsfx) engine_features &= ~EF_SFX;
        if(!numbgm) engine_features &= ~EF_BGM;
        tab = TAB_INFO;
        refresh = 1;
        return;
    }
    if(cur >= num) cur = num - 1;
    if(cur < 0) cur = 0;

    i = textlen(lang[EXPORT]) + 16;
    fields[0].x = 2; fields[0].y = screen_h - 23; fields[0].w = i; fields[0].h = 21;
    button(fields[0].x, fields[0].y, i, 21, pressedfield == 0, lang[EXPORT]);

    hscroll(screen_w - 10, 0, screen_h, scrcur, tot, pressedfield == 999);

    crop_w = screen_w - 10; crop_h = screen_h - 21 - 4;
    for(i = scrcur / 18; i < num && i * 18 - scrcur < crop_h; i++) {
        box(16, 3 + i * 18 - scrcur, screen_w - 32, 16, i == cur ? FG : BG - 0x080808);
        text(16, 3 + i * 18 - scrcur, i == cur ? BG : FG, audio[i].name);
    }
}
